__author__ = 'cain76'
import random
import math

from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GL.EXT.fog_coord import *
import pygame
from pygame.locals import *

import os
import sys

sys.path.insert(0, os.path.expanduser('~') + '/lib/python')

from mylib.gl3d.texture import *
from mylib.gl3d.drawobject import *
from mylib.gl3d.vec3 import *
fogColor = (0.8, 0.8, 0.8, 1.0)  # // Fog Colour
SCREEN_SIZE = (600, 800)


class GameCell:
    END_X = 1
    END_Y = 2
    END_Z = 4
    END_ALL = 7

    def __init__(self, _type=0.0, _x=0.0, _y=0.0, _z=0.0):
        self._type = _type
        self.pos = Vec3d(_x, _y, _z)
        self.dst = Vec3d(_x, _y, _z)
        self._size = 0.4
        self._speed = 1.0
        self._ry = 0.0
        self.do = DrawObject()

    def set_type(self, _type): self._type = _type

    def get_type(self): return self._type

    def set_pos(self, _x=0.0, _y=0.0, _z=0.0): self.pos = Vec3d(_x, _y, _z)

    def set_dest(self, _x=0.0, _y=0.0, _z=0.0):
        self.dst = Vec3d(_x, _y, _z)
        _s = self._get_dist()
        if _s < 1.1: self._speed = 1.0
        else: self._speed = _s

    def set_speed(self, _s): self._speed = _s

    def _get_dist(self): return self.dst.get_distance(self.pos)

    def vertex(self, sd=0, _x=0.0, _y=0.0, _z=0.0):
        if sd == 0:
            return Vec3d(self.dst.x + _x, self.dst.y + _y, self.dst.z + _z)
        return Vec3d(self.pos.x + _x, self.pos.y + _y, self.pos.z + _z)

    def update(self, ms):
        self._ry += ms * 100
        _s = ms
        ends = 0
        _s *= 2.0 * self._speed

        if self.pos.x < self.dst.x:
            self.pos.x += _s
            if self.pos.x > self.dst.x:
                self.pos.x = self.dst.x
                ends |= self.END_X
        elif self.pos.x > self.dst.x:
            self.pos.x -= _s
            if self.pos.x < self.dst.x:
                self.pos.x = self.dst.x
                ends |= self.END_X

        if self.pos.y < self.dst.y:
            self.pos.y += _s
            if self.pos.y > self.dst.y:
                self.pos.y = self.dst.y
                ends |= self.END_Y
        elif self.pos.y > self.dst.y:
            self.pos.y -= _s
            if self.pos.y < self.dst.y:
                self.pos.y = self.dst.y
                ends |= self.END_Y

        if self.pos.z < self.dst.z:
            self.pos.z += _s
            if self.pos.z > self.dst.z:
                self.pos.z = self.dst.z
                ends |= self.END_Z
        elif self.pos.z > self.dst.z:
            self.pos.z -= _s
            if self.pos.z < self.dst.z:
                self.pos.z = self.dst.z
                ends |= self.END_Z

        if self.pos == self.dst: return self.END_ALL
        return ends

    def render(self, _x=0.0, _y=0.0, _z=0.0):
        global textures
        glColor4f(1.0, 1.0, 1.0, 1.0)
        if self._type == 0:
            # glColor4f(0.0, 0.0, 0.0, 0.1)
            textures.bind('box0')
        elif self._type == 1:
            # glColor4f(1.0, 0.0, 0.0, 1.0)
            textures.bind('box1')
        elif self._type == 2:
            # glColor4f(1.0, 1.0, 0.0, 1.0)
            textures.bind('box2')
        elif self._type == 3:
            # glColor4f(0.0, 1.0, 0.0, 1.0)
            textures.bind('box3')
        elif self._type == 4:
            # glColor4f(0.0, 1.0, 1.0, 1.0)
            textures.bind('box4')
        elif self._type == 5:
            # glColor4f(0.0, 0.0, 1.0, 1.0)
            textures.bind('box5')
        elif self._type == 6:
            # glColor4f(1.0, 0.0, 1.0, 1.0)
            textures.bind('box6')
        elif self._type == 7:
            # glColor4f(0.5, 0.5, 0.0, 1.0)
            textures.bind('box7')
        else:
            # glColor4f(0.0, 0.5, 0.5, 1.0)
            textures.bind('box0')
        self.do.glDrawCube(self.pos.x + _x, self.pos.y + _y, self.pos.z + _z, self._size, 0.0, self._ry, 0.0)


class GameMap:
    _map = []
    _exp = []
    _w = 10
    _h = 20

    def __init__(self):
        for _x in range(self._w):
            _line = []
            for _y in range(self._h):
                if _y == 0: _line.append(GameCell(10, _x, _y))
                elif _x in (0, 9): _line.append(GameCell(10, _x, _y))
                else: _line.append(GameCell(0, _x, _y))
            self._map.append(_line)

    def set(self, _x, _y, _v):
        _x = int(_x)
        _y = int(_y)
        if 0 <= _x < self._w:
            if 0 <= _y < self._h:
                self._map[_x][_y].set_type(_v)

    def set_tet(self, _t):
        for o in _t.Obj:
            v = o.vertex()
            self._map[int(v.x)][int(v.y)].set_type(o.get_type())

    def check(self, vl):
        for v in vl:
            if 0 <= v.x < self._w:
                if 0 <= v.y < self._h:
                    if self._map[int(v.x)][int(v.y)].get_type() > 0:
                        return False

        return True

    def check_line(self):
        _y = self._h - 1
        while _y > 0:
            _x = self._w - 2
            _check = True
            while _x > 0:
                if self._map[_x][_y].get_type() == 0:
                    _check = False
                    break
                _x -= 1

            if _check:
                _x = 1
                while _x < self._w - 1:
                    _i = _y
                    self._map[_x].append(GameCell(0, _x, 20))
                    ex = GameCell(10, _x, _y)
                    ex.set_type(self._map[_x][_y].get_type())
                    ex.set_dest(0, _y, 0.0)
                    self._exp.append(ex)
                    self._map[_x].pop(_y)
                    while _i < self._h:
                        self._map[_x][_i].set_dest(_x, _i, 0.0)
                        _i += 1
                    _x += 1
            _y -= 1

    def update(self, ms):
        for ex in self._exp:
            ret = ex.update(ms)
            if ret == ex.END_ALL:
                self._exp.remove(ex)

        for _x in range(len(self._map)):
            for _y in range(len(self._map[_x])):
                self._map[_x][_y].update(ms)

    def render(self):
        glColor4f(1.0, 1.0, 1.0, 0.3)
        for _x in range(len(self._map)):
            for _y in range(len(self._map[_x])):
                if self._map[_x][_y].get_type() > 0.0:
                    self._map[_x][_y].render()

        for ex in self._exp:
            ex.render()


class GameItem:
    Obj = []
    pos = Vec3d(0.0, 0.0, 0.0)
    dest = Vec3d(0.0, 0.0, 0.0)
    END_X = 1
    END_Y = 2
    END_Z = 4
    END_ALL = 7

    def __init__(self):
        self.Obj = []
        self._angle = 0.0

    def update(self, ms):
        self._angle += ms * 100.0
        ret = 0
        for v in self.Obj:
            r = v.update(ms)
            ret |= r
        self.pos = self.Obj[0].vertex(1)

        return ret

    def render(self, _x=0.0, _y=0.0, _z=0.0):
        # glRotatef(self._angle, 0.0, 0.0, 1.0)
        for v in self.Obj:
            v.render(_x, _y, _z)
        # glRotatef(-self._angle, 0.0, 0.0, 1.0)


class TetrisItem(GameItem):
    _vl = []

    def __init__(self, i, r=0):
        GameItem.__init__(self)
        self._type = i
        self._rot = r
        self.set_type(i, r)

    @staticmethod
    def get_type_vertex_list(i, n):
        vl = []
        if i == 1:  # I
            if n == 0: vl = [Vec3d(0, 0), Vec3d(-2, 0), Vec3d(-1, 0), Vec3d(1, 0)]
            elif n == 1: vl = [Vec3d(0, 0), Vec3d(0, -2), Vec3d(0, -1), Vec3d(0, 1)]
            elif n == 2: vl = [Vec3d(0, 0), Vec3d(-1, 0), Vec3d(1, 0), Vec3d(2, 0)]
            elif n == 3: vl = [Vec3d(0, 0), Vec3d(0, -1), Vec3d(0, 1), Vec3d(0, 2)]
        elif i == 2:  # O
            if n == 0: vl = [Vec3d(0, 0), Vec3d(-1, 0), Vec3d(-1, -1), Vec3d(0, -1)]
            elif n == 1: vl = [Vec3d(0, 0), Vec3d(-1, 0), Vec3d(-1, -1), Vec3d(0, -1)]
            elif n == 2: vl = [Vec3d(0, 0), Vec3d(-1, 0), Vec3d(-1, -1), Vec3d(0, -1)]
            elif n == 3: vl = [Vec3d(0, 0), Vec3d(-1, 0), Vec3d(-1, -1), Vec3d(0, -1)]
        elif i == 3:  # T
            if n == 0: vl = [Vec3d(0, 0), Vec3d(-1, 0), Vec3d(0, -1), Vec3d(1, 0)]
            elif n == 1: vl = [Vec3d(0, 0), Vec3d(0, -1), Vec3d(1, 0), Vec3d(0, 1)]
            elif n == 2: vl = [Vec3d(0, 0), Vec3d(1, 0), Vec3d(0, 1), Vec3d(-1, 0)]
            elif n == 3: vl = [Vec3d(0, 0), Vec3d(0, 1), Vec3d(-1, 0), Vec3d(0, -1)]
        elif i == 4:  # L
            if n == 0: vl = [Vec3d(0, 0), Vec3d(-1, 0), Vec3d(1, 0), Vec3d(1, 1)]
            elif n == 1: vl = [Vec3d(0, 0), Vec3d(1, -1), Vec3d(0, -1), Vec3d(0, 1)]
            elif n == 2: vl = [Vec3d(0, 0), Vec3d(-1, -1), Vec3d(-1, 0), Vec3d(1, 0)]
            elif n == 3: vl = [Vec3d(0, 0), Vec3d(-1, 1), Vec3d(0, 1), Vec3d(0, -1)]
        elif i == 5:  # J
            if n == 0: vl = [Vec3d(0, 0), Vec3d(-1, 1), Vec3d(-1, 0), Vec3d(1, 0)]
            elif n == 1: vl = [Vec3d(0, 0), Vec3d(1, 1), Vec3d(0, 1), Vec3d(0, -1)]
            elif n == 2: vl = [Vec3d(0, 0), Vec3d(1, -1), Vec3d(1, 0), Vec3d(-1, 0)]
            elif n == 3: vl = [Vec3d(0, 0), Vec3d(-1, -1), Vec3d(0, -1), Vec3d(0, 1)]
        elif i == 6:  # Z
            if n == 0: vl = [Vec3d(0, 0), Vec3d(-1, -1), Vec3d(0, -1), Vec3d(1, 0)]
            elif n == 1: vl = [Vec3d(0, 0), Vec3d(1, -1), Vec3d(1, 0), Vec3d(0, 1)]
            elif n == 2: vl = [Vec3d(0, 0), Vec3d(-1, -1), Vec3d(0, -1), Vec3d(1, 0)]
            elif n == 3: vl = [Vec3d(0, 0), Vec3d(1, -1), Vec3d(1, 0), Vec3d(0, 1)]
        elif i == 7:  # S
            if n == 0: vl = [Vec3d(0, 0), Vec3d(-1, 0), Vec3d(0, -1), Vec3d(1, -1)]
            elif n == 1: vl = [Vec3d(0, 0), Vec3d(0, 1), Vec3d(-1, 0), Vec3d(-1, -1)]
            elif n == 2: vl = [Vec3d(0, 0), Vec3d(-1, 0), Vec3d(0, -1), Vec3d(1, -1)]
            elif n == 3: vl = [Vec3d(0, 0), Vec3d(0, 1), Vec3d(-1, 0), Vec3d(-1, -1)]
        return vl

    def set_type(self, i, n):
        vl = self.get_type_vertex_list(i, n)
        self._vl = list(vl)
        self.make_object(i, vl)

    def make_object(self, i, vl):
        self.Obj = []
        for v in vl:
            gc = GameCell(i, v.x, v.y, v.z)
            self.Obj.append(gc)

    def set_pos(self, _x=0.0, _y=0.0, _z=0.0):
        i = 0
        for obj in self.Obj:
            obj.set_pos(self._vl[i].x + _x, self._vl[i].y + _y, self._vl[i].z + _z)
            i += 1
        self.pos = Vec3d(_x, _y, _z)

    def set_dest(self, _x=0.0, _y=0.0, _z=0.0):
        i = 0
        for obj in self.Obj:
            obj.set_dest(self._vl[i].x + _x, self._vl[i].y + _y, self._vl[i].z + _z)
            i += 1
        self.dest = Vec3d(_x, _y, _z)

    def rotate(self, _next=1):
        if _next < 0: _next += 4
        _, self._rot = divmod(self._rot + _next, 4)
        vl = self.get_type_vertex_list(self._type, self._rot)
        self._vl = list(vl)
        for i in range(len(vl)):
            self.Obj[i].set_dest(self.dest.x + vl[i].x, self.dest.y + vl[i].y, self.dest.z + vl[i].z)
            # self.Obj[i].set_speed(1.0)

    def vertexes(self, sd=0, _x=0.0, _y=0.0, _z=0.0):
        vl = []
        for obj in self.Obj:
            vl.append(obj.vertex(sd, _x, _y, _z))
        return vl

textures = None


def gl_ext_init():
    """ Determine if the fog coord extentsion is availble """

    # After calling this, we will be able to invoke glFogCoordEXT ()
    if not glInitFogCoordEXT():
        print("Help!  No GL_EXT_ForCoord")
        exit()

    return True


def gl_init():  # We call this right after our OpenGL window is created.
    global fogColor

    if not gl_ext_init():  # // Check And Enable Fog Extension If Available
        return False  # // Return False If Extension Not Supported
    glEnable(GL_LIGHT0)
    glEnable(GL_LIGHTING)
    glEnable(GL_COLOR_MATERIAL)

    glEnable(GL_TEXTURE_2D)  # // Enable Texture Mapping
    glClearColor(0.0, 0.0, 0.0, 0.5)  # // Black Background
    glClearDepth(1.0)  # // Depth Buffer Setup
    glDepthFunc(GL_LEQUAL)  # // The Type Of Depth Testing
    glEnable(GL_DEPTH_TEST)  # // Enable Depth Testing
    glShadeModel(GL_SMOOTH)  # // Select Smooth Shading
    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST)  # // Set Perspective Calculations To Most Accurate

    # // Set Up Fog
    glEnable(GL_FOG)  # // Enable Fog
    glFogi(GL_FOG_MODE, GL_LINEAR)  # // Fog Fade Is Linear
    glFogfv(GL_FOG_COLOR, fogColor)  # // Set The Fog Color
    glFogf(GL_FOG_START, 0.0)  # // Set The Fog Start
    glFogf(GL_FOG_END, 1.0)  # // Set The Fog End
    glHint(GL_FOG_HINT, GL_NICEST)  # // Per-Pixel Fog Calculation
    glFogi(GL_FOG_COORDINATE_SOURCE_EXT, GL_FOG_COORDINATE_EXT)  # // Set Fog Based On Vertice Coordinates

    return True  # // Return TRUE (Initialization Successful)


# The function called when our window is resized (which shouldn't happen if you enable fullscreen, below)
def gl_resize(width, height):
    if height == 0:  # Prevent A Divide By Zero If The Window Is Too Small
        height = 1

    glViewport(0, 0, width, height)  # Reset The Current Viewport And Perspective Transformation
    glMatrixMode(GL_PROJECTION)
    glLoadIdentity()
    # // field of view, aspect ratio, near and far
    # This will squash and stretch our objects as the window is resized.
    gluPerspective(45.0, float(width) / float(height), 0.1, 500.0)

    glMatrixMode(GL_MODELVIEW)
    glLoadIdentity()


def run():
    global textures

    pygame.init()
    pygame.display.set_mode(SCREEN_SIZE, HWSURFACE | OPENGL | DOUBLEBUF)

    gl_init()
    gl_resize(SCREEN_SIZE[0], SCREEN_SIZE[1])

    textures = Textures()

    usr_home = os.path.expanduser('~')
    data_home = usr_home + '/src/res/'
    # obj = OBJ(data_home + 'barrel.obj')
    if textures.load('box0', data_home + 'Wall.bmp'):
        print('Texture Loaded')
    if textures.load('box1', data_home + "box.png"):
        print('Texture Loaded')
    if textures.load('box2', data_home + "box2.png"):
        print('Texture Loaded')
    if textures.load('box3', data_home + "box-texture1.png"):
        print('Texture Loaded')
    if textures.load('box4', data_home + "crate.jpg"):
        print('Texture Loaded')
    if textures.load('box5', data_home + "crate2.jpg"):
        print('Texture Loaded')
    if textures.load('box6', data_home + "ammoc01.jpg"):
        print('Texture Loaded')
    if textures.load('box7', data_home + "cpack01.jpg"):
        print('Texture Loaded')

    clock = pygame.time.Clock()

    glMaterial(GL_FRONT, GL_AMBIENT, (0.1, 0.1, 0.1, 1.0))
    glMaterial(GL_FRONT, GL_DIFFUSE, (1.0, 1.0, 1.0, 1.0))

    # This object renders the 'map'
    game_map = GameMap()
    tet = TetrisItem(random.randrange(7) + 1)
    tet.set_pos(5, 19, 0)
    tet.set_dest(5, 19, 0)

    eye = Vec3d(0., 0., 0.)
    rot = Vec3d(0., 0., 0.)
    while True:
        for event in pygame.event.get():
            if event.type == QUIT:
                return
            if event.type == KEYUP and event.key == K_ESCAPE:
                return

            if event.type == KEYDOWN and event.key == K_SPACE:
                while game_map.check(tet.vertexes(0, 0.0, -1.0, 0.0)):
                    tet.set_dest(tet.dest.x, tet.dest.y - 1, tet.dest.z)
            if event.type == KEYDOWN and event.key == K_UP:
                tet.rotate()
                if not game_map.check(tet.vertexes()):
                    tet.rotate(-1)
            if event.type == KEYDOWN and event.key == K_LEFT:
                if game_map.check(tet.vertexes(0, -1.0, 0.0, 0.0)):
                    tet.set_dest(tet.dest.x - 1, tet.dest.y, tet.dest.z)
            if event.type == KEYDOWN and event.key == K_RIGHT:
                if game_map.check(tet.vertexes(0, 1.0, 0.0, 0.0)):
                    tet.set_dest(tet.dest.x + 1, tet.dest.y, tet.dest.z)

        # Clear the screen, and z-buffer
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)

        time_passed = clock.tick(29)
        time_passed_seconds = time_passed / 1000.

        pressed = pygame.key.get_pressed()

        # if pressed[K_LEFT]: eye.x += 0.2
        # elif pressed[K_RIGHT]: eye.x -= 0.2
        # if pressed[K_UP]: eye.y -= 0.2
        # elif pressed[K_DOWN]: eye.y += 0.2
        if pressed[K_w]: eye.z += 0.2
        elif pressed[K_s]: eye.z -= 0.2
        if pressed[K_a]: rot.y -= 1.0
        elif pressed[K_d]: rot.y += 1.0

        # Light must be transformed as well
        # glLight(GL_LIGHT0, GL_POSITION, (0, -10., 0., 0))
        glLightfv(GL_LIGHT0, GL_POSITION, (5., 10., 10, 0.0))
        glLightfv(GL_LIGHT0, GL_AMBIENT, (0.5, 0.5, 0.5, 1.0))
        glLightfv(GL_LIGHT0, GL_DIFFUSE, (0.8, 0.8, 0.8, 1.0))
        glTranslatef(eye.x, eye.y, eye.z)
        # glRotatef(rot.y, 0., 1., 0.)
        glTranslatef(-5.0, -10., -30.)
        game_map.update(time_passed_seconds)
        # Render the map
        ret = tet.update(time_passed_seconds)
        if (ret & tet.END_Y) == tet.END_Y:
            if game_map.check(tet.vertexes(0, 0.0, -1.0, 0.0)):
                tet.set_dest(tet.dest.x, tet.dest.y - 1, tet.dest.z)
            elif ret == tet.END_ALL:
                game_map.set_tet(tet)
                game_map.check_line()
                tet = TetrisItem(random.randrange(7) + 1)
                tet.set_pos(5, 19, 0)
                tet.set_dest(5, 19, 0)
                if not game_map.check(tet.vertexes()):
                    print('Game Over')
                    return

        game_map.render()
        tet.render(0, 0, 0)
        glTranslatef(0.0, 0.0, -350.0)
        # glCallList(obj.gl_list)
        glTranslatef(0.0, 0.0, 350.0)
        glTranslatef(5., 10., 30.)
        # glRotatef(-rot.y, 0., 1., 0.)
        glTranslatef(-eye.x, -eye.y, -eye.z)

        # Show the screen
        pygame.display.flip()
    textures.clear()


if __name__ == '__main__':
    run()